Project 2 (EAS 509: Fall’23)
Project Title: Time Series Analysis &
Forecasting of Oil Sales
Team Members:
- Sujay Shrivastava (50496221) (sujayshr)
- Utkarsh Mathur (50495131) (umathur)
- Venkata Lakshmi Krishna Tejaswi Gudimetla (50496378) (vgudimet)
Importing Libraries and Data
# Visualization and Analysis
library(dplyr)
library(tidyr)
library(ggplot2)
library(ggdendro)
library(readxl)
library(plotly)
suppressWarnings({
library(readxl)
})
# Modeling and Inference
library(TSA)
library(forecast)
library(astsa)
# Importind Data.
# NOTE: Please store the oil.csv file in the directory of this file.
df = read.csv('oil.csv')
summary(df)
date dcoilwtico
Length:1218 Min. : 26.19
Class :character 1st Qu.: 46.41
Mode :character Median : 53.19
Mean : 67.71
3rd Qu.: 95.66
Max. :110.62
NA's :43
From the above summary we can observe that there are 1218 datapoints
in out dataset and 43 missing values. We will address the missing values
later on.
Plotting Original Data
plot1 <- df |>
plot_ly(type="scatter", mode="lines") |>
add_trace(x = ~date, y = ~dcoilwtico, name="Daily Sale") |>
layout(showlegend=FALSE, plot_bgcolor = "white")
options(warn=-1)
plot1
Warning: Can't display both discrete & non-discrete data on same axisWarning: Can't display both discrete & non-discrete data on same axis
Imputing Missing Value
Based on the analysis of the plot we have decided to impute missing
value with the next value observed in the time series.
df_new <- df %>% fill(dcoilwtico, .direction="up")
summary(df_new)
date dcoilwtico
Length:1218 Min. : 26.19
Class :character 1st Qu.: 46.42
Mode :character Median : 53.19
Mean : 67.67
3rd Qu.: 95.59
Max. :110.62
We now plot the imputed dataset.
plot2 <- df_new |>
plot_ly(type="scatter", mode="lines") |>
add_trace(x = ~date, y = ~dcoilwtico, name="Daily Sale") |>
layout(showlegend=FALSE, plot_bgcolor = "white")
options(warn=-1)
plot2
Warning: Can't display both discrete & non-discrete data on same axisWarning: Can't display both discrete & non-discrete data on same axis
Upon plotting the imputed dataset we can observe that there is no
trend or seasonality observed over time. This is a very good instance to
test Exponential Smoothing methods.
components_df <- decompose(df_new)
Error in decompose(df_new) : time series has no or less than 2 periods
As it can be seen in the above code piece the data has no seasonality
as the time series in not decomposible.
ETS and Holt-Winters Models
Forecasts produced using exponential smoothing methods are weighted
averages of past observations, with the weights decaying exponentially
as the observations get older. In other words, the more recent the
observation the higher the associated weight.
Simple Exponential Smoothing
The simplest of exponentially smoothing methods is Simple Exponential
Smoothing (SES). This method is suitable for forecasting data with no
clear trend or seasonal pattern. For simple exponential smoothing, the
only component included is the level as observed in the below
forecasting and smoothing equation. The smoothing equation only uses
alpha to include the trend component of Time Series.

Holt-Winters Methods
Holt (1957) and Winters (1960) extended Holt's method to capture
seasonality. The Holt-Winters seasonal method comprises the forecast
equation and three smoothing equations — one for the level \(l_t\) one for the trend \(b_t\), and one for the seasonal component
\(s_t\) ,with corresponding smoothing
parameters \(\alpha\), \(\beta^*\), and \(\gamma\) respectively.
Additive Method:

Multiplicative Method:

Model Training
As the data doesn’t have seasonality in it (evident from the
decomposition of data) we can safely say that Holt-Winters method is of
lesser use here. So we are going to compare 2 models:
- ARIMA (trained without specifying p, d, and q)
- Exponential Smoothing
ARIMA Model
modelARIMA <- auto.arima(df_new['dcoilwtico'])
summary(modelARIMA)
Series: df_new["dcoilwtico"]
ARIMA(0,1,0)
sigma^2 = 1.449: log likelihood = -1952.37
AIC=3906.73 AICc=3906.73 BIC=3911.83
Training set error measures:
ME RMSE MAE MPE MAPE MASE ACF1
Training set -0.03759184 1.203095 0.8949041 -0.08030733 1.547907 0.9992644 -0.04614975
As observed, the best fit ARIMA model is ARIMA(p=0, d=1, q=0) with
AICc3906.73 and RMSE 1.203095.
fcARIMA <- forecast(modelARIMA, h=100)
autoplot(fcARIMA)

Exponential Smoothing model
dfETS <- as.ts(df_new['dcoilwtico'])
modelETS <- ets(dfETS)
summary(modelETS)
ETS(A,N,N)
Call:
ets(y = dfETS)
Smoothing parameters:
alpha = 0.954
Initial states:
l = 93.1356
sigma: 1.2028
AIC AICc BIC
9107.716 9107.735 9123.031
Training set error measures:
ME RMSE MAE MPE MAPE MASE ACF1
Training set -0.03953028 1.20184 0.8965985 -0.08424058 1.551493 1.001156 -0.0008160611
The best fitted Exponential Smoothing model has alpha 0.954, and its
has AICc of 9107.735 and RMSE score is 1.20184 which is better than the
performance of best fitted ARIMA model.
fcETS <- forecast(modelETS, h=100)
autoplot(fcETS)

Diagnosis and Forecasting Analysis
Upon training the dataset on ARIMA and ETS models we observe that
they yield similar RMSE scores. This metric is further supported in the
behavior of the 100 days forecast plots.
However, the RMSE score of the Exponential Smoothing (1.20184) is
better than ARIMA model (1.203095) so, for our data the the Exponential
Smoothing model (with alpha=0.954, beta=0, and gamma=0) is a better
model than ARIMA (p=0, d=1, q=0).
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIFByb2plY3QgMiAoRUFTIDUwOTogRmFsbCcyMykNCg0KIyMjIFsqKlByb2plY3QgVGl0bGUqKl17LnVuZGVybGluZX06IFRpbWUgU2VyaWVzIEFuYWx5c2lzICYgRm9yZWNhc3Rpbmcgb2YgT2lsIFNhbGVzDQoNCiMjIyBbVGVhbSBNZW1iZXJzXXsudW5kZXJsaW5lfToNCg0KMS4gIFN1amF5IFNocml2YXN0YXZhICg1MDQ5NjIyMSkgKHN1amF5c2hyKQ0KMi4gIFV0a2Fyc2ggTWF0aHVyICg1MDQ5NTEzMSkgKHVtYXRodXIpDQozLiAgVmVua2F0YSBMYWtzaG1pIEtyaXNobmEgVGVqYXN3aSBHdWRpbWV0bGEgKDUwNDk2Mzc4KSAodmd1ZGltZXQpDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBJbXBvcnRpbmcgTGlicmFyaWVzIGFuZCBEYXRhDQoNCmBgYHtyfQ0KIyBWaXN1YWxpemF0aW9uIGFuZCBBbmFseXNpcw0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdnZGVuZHJvKQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KHBsb3RseSkNCnN1cHByZXNzV2FybmluZ3Moew0KICBsaWJyYXJ5KHJlYWR4bCkNCn0pDQoNCiMgTW9kZWxpbmcgYW5kIEluZmVyZW5jZQ0KbGlicmFyeShUU0EpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeShhc3RzYSkNCmBgYA0KDQpgYGB7cn0NCiMgSW1wb3J0aW5kIERhdGEuDQojIE5PVEU6IFBsZWFzZSBzdG9yZSB0aGUgb2lsLmNzdiBmaWxlIGluIHRoZSBkaXJlY3Rvcnkgb2YgdGhpcyBmaWxlLg0KZGYgPSByZWFkLmNzdignb2lsLmNzdicpDQpzdW1tYXJ5KGRmKQ0KYGBgDQoNCkZyb20gdGhlIGFib3ZlIHN1bW1hcnkgd2UgY2FuIG9ic2VydmUgdGhhdCB0aGVyZSBhcmUgMTIxOCBkYXRhcG9pbnRzIGluIG91dCBkYXRhc2V0IGFuZCA0MyBtaXNzaW5nIHZhbHVlcy4gV2Ugd2lsbCBhZGRyZXNzIHRoZSBtaXNzaW5nIHZhbHVlcyBsYXRlciBvbi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIFBsb3R0aW5nIE9yaWdpbmFsIERhdGENCg0KYGBge3J9DQpwbG90MSA8LSBkZiB8Pg0KICBwbG90X2x5KHR5cGU9InNjYXR0ZXIiLCBtb2RlPSJsaW5lcyIpIHw+DQogIGFkZF90cmFjZSh4ID0gfmRhdGUsIHkgPSB+ZGNvaWx3dGljbywgbmFtZT0iRGFpbHkgU2FsZSIpIHw+DQogIGxheW91dChzaG93bGVnZW5kPUZBTFNFLCBwbG90X2JnY29sb3IgPSAid2hpdGUiKQ0Kb3B0aW9ucyh3YXJuPS0xKQ0KDQpwbG90MQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBJbXB1dGluZyBNaXNzaW5nIFZhbHVlDQoNCkJhc2VkIG9uIHRoZSBhbmFseXNpcyBvZiB0aGUgcGxvdCB3ZSBoYXZlIGRlY2lkZWQgdG8gaW1wdXRlIG1pc3NpbmcgdmFsdWUgd2l0aCB0aGUgbmV4dCB2YWx1ZSBvYnNlcnZlZCBpbiB0aGUgdGltZSBzZXJpZXMuDQoNCmBgYHtyfQ0KZGZfbmV3IDwtIGRmICU+JSBmaWxsKGRjb2lsd3RpY28sIC5kaXJlY3Rpb249InVwIikNCnN1bW1hcnkoZGZfbmV3KQ0KYGBgDQoNCldlIG5vdyBwbG90IHRoZSBpbXB1dGVkIGRhdGFzZXQuDQoNCmBgYHtyfQ0KcGxvdDIgPC0gZGZfbmV3IHw+DQogIHBsb3RfbHkodHlwZT0ic2NhdHRlciIsIG1vZGU9ImxpbmVzIikgfD4NCiAgYWRkX3RyYWNlKHggPSB+ZGF0ZSwgeSA9IH5kY29pbHd0aWNvLCBuYW1lPSJEYWlseSBTYWxlIikgfD4NCiAgbGF5b3V0KHNob3dsZWdlbmQ9RkFMU0UsIHBsb3RfYmdjb2xvciA9ICJ3aGl0ZSIpDQpvcHRpb25zKHdhcm49LTEpDQoNCnBsb3QyDQpgYGANCg0KVXBvbiBwbG90dGluZyB0aGUgaW1wdXRlZCBkYXRhc2V0IHdlIGNhbiBvYnNlcnZlIHRoYXQgdGhlcmUgaXMgbm8gdHJlbmQgb3Igc2Vhc29uYWxpdHkgb2JzZXJ2ZWQgb3ZlciB0aW1lLiBUaGlzIGlzIGEgdmVyeSBnb29kIGluc3RhbmNlIHRvIHRlc3QgRXhwb25lbnRpYWwgU21vb3RoaW5nIG1ldGhvZHMuDQoNCmBgYHtyfQ0KY29tcG9uZW50c19kZiA8LSBkZWNvbXBvc2UoZGZfbmV3KQ0KYGBgDQoNCkFzIGl0IGNhbiBiZSBzZWVuIGluIHRoZSBhYm92ZSBjb2RlIHBpZWNlIHRoZSBkYXRhIGhhcyBubyBzZWFzb25hbGl0eSBhcyB0aGUgdGltZSBzZXJpZXMgaW4gbm90IGRlY29tcG9zaWJsZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEVUUyBhbmQgSG9sdC1XaW50ZXJzIE1vZGVscw0KDQpGb3JlY2FzdHMgcHJvZHVjZWQgdXNpbmcgZXhwb25lbnRpYWwgc21vb3RoaW5nIG1ldGhvZHMgYXJlIHdlaWdodGVkIGF2ZXJhZ2VzIG9mIHBhc3Qgb2JzZXJ2YXRpb25zLCB3aXRoIHRoZSB3ZWlnaHRzIGRlY2F5aW5nIGV4cG9uZW50aWFsbHkgYXMgdGhlIG9ic2VydmF0aW9ucyBnZXQgb2xkZXIuIEluIG90aGVyIHdvcmRzLCB0aGUgbW9yZSByZWNlbnQgdGhlIG9ic2VydmF0aW9uIHRoZSBoaWdoZXIgdGhlIGFzc29jaWF0ZWQgd2VpZ2h0Lg0KDQojIyMgU2ltcGxlIEV4cG9uZW50aWFsIFNtb290aGluZw0KDQpUaGUgc2ltcGxlc3Qgb2YgZXhwb25lbnRpYWxseSBzbW9vdGhpbmcgbWV0aG9kcyBpcyBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChTRVMpLiBUaGlzIG1ldGhvZCBpcyBzdWl0YWJsZSBmb3IgZm9yZWNhc3RpbmcgZGF0YSB3aXRoIG5vIGNsZWFyIHRyZW5kIG9yIHNlYXNvbmFsIHBhdHRlcm4uIEZvciBzaW1wbGUgZXhwb25lbnRpYWwgc21vb3RoaW5nLCB0aGUgb25seSBjb21wb25lbnQgaW5jbHVkZWQgaXMgdGhlIGxldmVsIGFzIG9ic2VydmVkIGluIHRoZSBiZWxvdyBmb3JlY2FzdGluZyBhbmQgc21vb3RoaW5nIGVxdWF0aW9uLiBUaGUgc21vb3RoaW5nIGVxdWF0aW9uIG9ubHkgdXNlcyBhbHBoYSB0byBpbmNsdWRlIHRoZSB0cmVuZCBjb21wb25lbnQgb2YgVGltZSBTZXJpZXMuDQoNCiFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjMtMTItMDYlMjAyMjAxMjAucG5nKQ0KDQojIyMgSG9sdC1XaW50ZXJzIE1ldGhvZHMNCg0KSG9sdCAoMTk1NykgYW5kIFdpbnRlcnMgKDE5NjApIGV4dGVuZGVkIEhvbHRcJ3MgbWV0aG9kIHRvIGNhcHR1cmUgc2Vhc29uYWxpdHkuIFRoZSBIb2x0LVdpbnRlcnMgc2Vhc29uYWwgbWV0aG9kIGNvbXByaXNlcyB0aGUgZm9yZWNhc3QgZXF1YXRpb24gYW5kIHRocmVlIHNtb290aGluZyBlcXVhdGlvbnMgLS0tIG9uZSBmb3IgdGhlIGxldmVsICRsX3QkIG9uZSBmb3IgdGhlIHRyZW5kICRiX3QkLCBhbmQgb25lIGZvciB0aGUgc2Vhc29uYWwgY29tcG9uZW50ICRzX3QkICx3aXRoIGNvcnJlc3BvbmRpbmcgc21vb3RoaW5nIHBhcmFtZXRlcnMgJFxhbHBoYSQsICRcYmV0YV4qJCwgYW5kICRcZ2FtbWEkIHJlc3BlY3RpdmVseS4NCg0KIyMjIyBBZGRpdGl2ZSBNZXRob2Q6DQoNCiFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjMtMTItMDYlMjAyMjEwNDgucG5nKQ0KDQojIyMjIE11bHRpcGxpY2F0aXZlIE1ldGhvZDoNCg0KIVtdKGltYWdlcy9TY3JlZW5zaG90JTIwMjAyMy0xMi0wNiUyMDIyMTEwNy5wbmcpDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBNb2RlbCBUcmFpbmluZw0KDQpBcyB0aGUgZGF0YSBkb2Vzbid0IGhhdmUgc2Vhc29uYWxpdHkgaW4gaXQgKGV2aWRlbnQgZnJvbSB0aGUgZGVjb21wb3NpdGlvbiBvZiBkYXRhKSB3ZSBjYW4gc2FmZWx5IHNheSB0aGF0IEhvbHQtV2ludGVycyBtZXRob2QgaXMgb2YgbGVzc2VyIHVzZSBoZXJlLiBTbyB3ZSBhcmUgZ29pbmcgdG8gY29tcGFyZSAyIG1vZGVsczoNCg0KMS4gIEFSSU1BICh0cmFpbmVkIHdpdGhvdXQgc3BlY2lmeWluZyBwLCBkLCBhbmQgcSkNCjIuICBFeHBvbmVudGlhbCBTbW9vdGhpbmcNCg0KIyMjIEFSSU1BIE1vZGVsDQoNCmBgYHtyfQ0KbW9kZWxBUklNQSA8LSBhdXRvLmFyaW1hKGRmX25ld1snZGNvaWx3dGljbyddKQ0Kc3VtbWFyeShtb2RlbEFSSU1BKQ0KYGBgDQoNCkFzIG9ic2VydmVkLCB0aGUgYmVzdCBmaXQgQVJJTUEgbW9kZWwgaXMgQVJJTUEocD0wLCBkPTEsIHE9MCkgd2l0aCBBSUNjMzkwNi43MyBhbmQgUk1TRSAxLjIwMzA5NS4NCg0KYGBge3J9DQpmY0FSSU1BIDwtIGZvcmVjYXN0KG1vZGVsQVJJTUEsIGg9MTAwKQ0KYXV0b3Bsb3QoZmNBUklNQSkNCmBgYA0KDQojIyMgRXhwb25lbnRpYWwgU21vb3RoaW5nIG1vZGVsDQoNCmBgYHtyfQ0KZGZFVFMgPC0gYXMudHMoZGZfbmV3WydkY29pbHd0aWNvJ10pDQptb2RlbEVUUyA8LSBldHMoZGZFVFMpIA0Kc3VtbWFyeShtb2RlbEVUUykNCmBgYA0KDQpUaGUgYmVzdCBmaXR0ZWQgRXhwb25lbnRpYWwgU21vb3RoaW5nIG1vZGVsIGhhcyBhbHBoYSAwLjk1NCwgYW5kIGl0cyBoYXMgQUlDYyBvZiA5MTA3LjczNSBhbmQgUk1TRSBzY29yZSBpcyAxLjIwMTg0IHdoaWNoIGlzIGJldHRlciB0aGFuIHRoZSBwZXJmb3JtYW5jZSBvZiBiZXN0IGZpdHRlZCBBUklNQSBtb2RlbC4NCg0KYGBge3J9DQpmY0VUUyA8LSBmb3JlY2FzdChtb2RlbEVUUywgaD0xMDApDQphdXRvcGxvdChmY0VUUykNCmBgYA0KDQojIyMgRGlhZ25vc2lzIGFuZCBGb3JlY2FzdGluZyBBbmFseXNpcw0KDQpVcG9uIHRyYWluaW5nIHRoZSBkYXRhc2V0IG9uIEFSSU1BIGFuZCBFVFMgbW9kZWxzIHdlIG9ic2VydmUgdGhhdCB0aGV5IHlpZWxkIHNpbWlsYXIgUk1TRSBzY29yZXMuIFRoaXMgbWV0cmljIGlzIGZ1cnRoZXIgc3VwcG9ydGVkIGluIHRoZSBiZWhhdmlvciBvZiB0aGUgMTAwIGRheXMgZm9yZWNhc3QgcGxvdHMuDQoNCkhvd2V2ZXIsIHRoZSBSTVNFIHNjb3JlIG9mIHRoZSBFeHBvbmVudGlhbCBTbW9vdGhpbmcgKDEuMjAxODQpIGlzIGJldHRlciB0aGFuIEFSSU1BIG1vZGVsICgxLjIwMzA5NSkgc28sIGZvciBvdXIgZGF0YSB0aGUgdGhlIEV4cG9uZW50aWFsIFNtb290aGluZyBtb2RlbCAod2l0aCBhbHBoYT0wLjk1NCwgYmV0YT0wLCBhbmQgZ2FtbWE9MCkgaXMgYSBiZXR0ZXIgbW9kZWwgdGhhbiBBUklNQSAocD0wLCBkPTEsIHE9MCkuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBSZWZlcmVuY2VzDQoNCjEuICA8aHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnLz4NCjIuICBIeW5kbWFuLCBSLkouLCAmIEF0aGFuYXNvcG91bG9zLCBHLiAoMjAyMSkgKkZvcmVjYXN0aW5nOiBwcmluY2lwbGVzIGFuZCBwcmFjdGljZSosIDNyZCBlZGl0aW9uLCBPVGV4dHM6IE1lbGJvdXJuZSwgQXVzdHJhbGlhLiBPVGV4dHMuY29tL2ZwcDMuIEFjY2Vzc2VkIG9uIDQgRGVjZW1iZXIgMjAyMy4gKDxodHRwczovL290ZXh0cy5jb20vZnBwMy8+KSBcKlwqDQozLiAgIDxodHRwczovL3JvYmpoeW5kbWFuLmNvbS9leHBzbW9vdGgvPiAoQWNjZXNzZWQgb24gNCBEZWNlbWJlciAyMDIzKQ0KNC4gIDxodHRwczovL21lZGl1bS5jb20vYW5hbHl0aWNzLXZpZGh5YS9hLXRob3JvdWdoLWludHJvZHVjdGlvbi10by1ob2x0LXdpbnRlcnMtZm9yZWNhc3RpbmctYzIxODEwYjhjMGU2PiAoQWNjZXNzZWQgb24gNCBEZWNlbWJlciAyMDIzKQ0KNS4gIDxodHRwczovL3d3dy5tYWNoaW5lbGVhcm5pbmdwbHVzLmNvbS90aW1lLXNlcmllcy9hcmltYS1tb2RlbC10aW1lLXNlcmllcy1mb3JlY2FzdGluZy1weXRob24vPiAoQWNjZXNzZWQgb24gNSBEZWNlbWJlciAyMDIzKQ0KNi4gIDxodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDIzLzAxL2ltcHV0YXRpb24taW4tci10b3AtMy13YXlzLWZvci1pbXB1dGluZy1taXNzaW5nLWRhdGEvPiAoQWNjZXNzZWQgb24gNSBEZWNlbWJlciAyMDIzKQ0KNy4gIDxodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2ZpbGwuaHRtbD4gKEFjY2Vzc2VkIG9uIDYgRGVjZW1iZXIgMjAyMykNCg0KXCpcKiBXZSB1c2VkIHRoZSBleGFjdCB0ZXh0IGZyb20gdGhpcyBib29rIHRvIG1haW50YWluIHRoZSBhY2N1cmFjeSBvZiB0aGVvcmV0aWNhbCBpbmZvcm1hdGlvbiBwcm92aWRlZC4NCg==